/*
 * Copyright (C) 2021, WeiStudio
 *
 * License: Apache-2.0
 *
 * Change Logs:
 * Date			Author		Note
 * 2021-07-20	WeiStudio		the first version
 *
 */


#include "qe_spi.h"
#include "qe_log.h"
#include "qe_memory.h"
#include "qe_assert.h"
#include "qe_errno.h"



QELOG_DOMAIN(QELOG_DOMAIN_SPI);



/**
 * @brief Spi bus attach to a device
 * @param[in] spi: spi device
 * @param[in] name: spi device name
 * @param[in] bus: bus name
 * @param[in] user_data: spi device user data
 * @return qe_ret 
 */
qe_ret qe_spi_bus_attach_device(qe_spi_dev *spi, qe_const_str name,
	qe_const_str bus_name, qe_ptr user_data)
{
	qe_ret ret;
	qe_dev *bus;

	if (!spi || !name || !bus_name) {
		qe_error("invalid params");
		return qe_err_param;
	}

	bus = qe_dev_find(bus_name);
	if ((bus != QE_NULL) && (bus->type == QE_DEV_SPI_BUS)) {
		spi->bus = (qe_spi_bus *)bus;
		ret = qe_spidev_register(spi, name);
		if (ret != qe_ok) {
			return ret;
		}
		qe_memset(&spi->config, 0, sizeof(spi->config));
		spi->parent.priv = user_data;
		qe_debug("%s attach to bus:%s", name, bus_name);
		return qe_ok;
	}

	return qe_err_notexist;
}

/**
 * @brief Spi transfer without lock
 * @param[in] dev: spi device
 * @param[in] send_buf: send buffer
 * @param[in] recv_buf: recv buffer
 * @param[in] length: transfer length
 * @return >0:success <0:error
 */
qe_int qe_spi_transfer(qe_spi_dev *spi, qe_const_ptr send_buf,
	qe_ptr recv_buf, qe_size length)
{
	qe_int n;
	qe_ret ret;
	qe_spi_bus *bus;
	qe_spi_message message;

	qe_assert(spi != QE_NULL);
	qe_assert(spi->bus != QE_NULL);

	bus = spi->bus;

	if (bus->owner != spi) {

		/* not the same owner as current, re-configure SPI bus */
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			qe_error("spi bus %s config error:%d", ret);
			return -(ret);
		}

		/* set SPI bus owner */
		bus->owner = spi;
		qe_debug("spi bus %s set owner %s", bus->parent.name, 
			spi->parent.name);
	}

	/* initial message */
	message.send_buf   = send_buf;
	message.recv_buf   = recv_buf;
	message.length     = length;
	message.cs_take    = 1;
	message.cs_release = 1;
	message.next       = QE_NULL;

	/* transfer message */
	return bus->ops->xfer(spi, &message);
}

/**
 * @brief Spi send from buffer and receive to buffer
 * @param[in] spi: spi device
 * @param[in] send_buf: send buffer
 * @param[in] send_length: send buffer length
 * @param[out] recv_buf: recv buffer
 * @param[in] recv_length: recv buffer length
 * @return >0:success <0:error
 */
qe_int qe_spi_send_recv(qe_spi_dev *spi, 
	qe_const_ptr send_buf, qe_size send_length, 
	qe_ptr recv_buf, qe_size recv_length)
{
	qe_int n;
	qe_ret ret;
	qe_spi_bus *bus;
	qe_spi_message message;
	
	qe_assert(spi != QE_NULL);
	qe_assert(spi->bus != QE_NULL);

	bus = spi->bus;

	if (bus->owner != spi) {
		
		/* not the same owner as current, re-configure SPI bus */
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			/* configure SPI bus failed */
			qe_error("spi bus %s config error:%d", ret);
			return -(ret);
		}

		/* set SPI bus owner */
		bus->owner = spi;
		qe_debug("spi bus %s set owner %s", bus->parent.name, 
			spi->parent.name);
	}

	/* send data */
	message.send_buf   = send_buf;
	message.recv_buf   = QE_NULL;
	message.length     = send_length;
	message.cs_take    = 1;
	message.cs_release = 0;
	message.next       = QE_NULL;

	n = bus->ops->xfer(spi, &message);
	if (n <= 0) {
		qe_error("spi bus send error:%d", n);
		return n;
	}

	/* recv data */
	message.send_buf   = QE_NULL;
	message.recv_buf   = recv_buf;
	message.length     = recv_length;
	message.cs_take    = 0;
	message.cs_release = 1;
	message.next       = QE_NULL;

	return bus->ops->xfer(spi, &message);
}

/**
 * @brief  Spi transfer message list
 * @param[in] spi: spi device
 * @param[in] message: message list
 * @return success transferd message index
 */
qe_spi_message *qe_spi_transfer_message(qe_spi_dev *spi,
	qe_spi_message *message)
{
	qe_int n;
	qe_ret ret;
	qe_spi_bus *bus;
	qe_spi_message *index;

	qe_assert(spi != QE_NULL);

	if (!spi || !message) {
		qe_error("invalid params");
		return QE_NULL;
	}

	if (!spi->bus) {
		qe_error("spi dev %s no bus", spi->parent.name);
		return QE_NULL;
	}

	bus = spi->bus;

	if (bus->owner != spi) {
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			qe_error("spi bus %s config error:%d", bus->parent.name);
			return QE_NULL;
		} else {
			bus->owner = spi;
			qe_debug("spi bus %s set owner %s", bus->parent.name, 
				spi->parent.name);
		}
	}

	/* transmit each message */
	index = message;
	while (index != QE_NULL) {
		n = bus->ops->xfer(spi, index);
		if (n <= 0) {
			qe_error("msg %p xfer error:%d", index, n);
			return index;
		}

		index = index->next;
	}

	return index;
}

/**
 * @brief Spi bus set lock
 * @param[in] bus: Spi bus
 * @param[in] lock: lock reference
 * @param[in] acquire: lock acquire function
 * @param[in] release: lock release function
 * @return qe_ret
 */
qe_ret qe_spi_bus_setlock(qe_spi_bus *bus, qe_ptr lock,
	qe_lock_acquire acquire,
	qe_lock_release release)
{
	if (!bus || !lock || !acquire || !release) {
		qe_error("invalid params");
		return qe_err_param;
	}

	bus->lock.lock    = lock;
	bus->lock.acquire = acquire;
	bus->lock.release = release;
	bus->multi_access = 1;

	qe_debug("bus %s set lock %p", bus->parent.name);

	return qe_ok;
}

/**
 * @brief Spi send from buffer and receive to buffer with lock
 * @param[in] spi: spi device
 * @param[in] send_buf: send buffer
 * @param[in] send_length: send buffer length
 * @param[out] recv_buf: recv buffer
 * @param[in] recv_length: recv buffer length
 * @param[in] wait: wait timeout
 * @return >0:success <0:error
 */
qe_int qe_spi_send_recv_wait(qe_spi_dev *spi, 
	qe_const_ptr send_buf, qe_size send_length, 
	qe_ptr recv_buf, qe_size recv_length, qe_uint wait)
{
	qe_int n;
	qe_ret ret;
	qe_lock *lock;
	qe_spi_bus *bus;
	qe_spi_message message;
	
	if (!spi) {
		qe_error("spi null");
		return -(qe_err_param);
	}

	if (!spi->bus) {
		qe_error("spi dev %s no bus", spi->parent.name);
		return -(qe_err_param);
	}

	bus = spi->bus;
	if (!bus->lock.lock || !bus->lock.acquire || !bus->lock.release) {
		qe_error("spi bus %s no lock", bus->parent.name);
		return -(qe_err_notsupport);
	}

	lock = &bus->lock;
	ret = lock->acquire(lock, wait);
	if (ret != qe_ok) {
		qe_error("spi bus %s lock error:%d", bus->parent.name, ret);
		return -(ret);
	}

	if (bus->owner != spi) {
		
		/* not the same owner as current, re-configure SPI bus */
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			/* configure SPI bus failed */
			qe_error("spi bus %s config error:%d", ret);
			n = -(ret);
			goto __exit;
		}

		/* set SPI bus owner */
		bus->owner = spi;
		qe_debug("spi bus %s set owner %s", bus->parent.name, 
			spi->parent.name);
	}

	/* send data */
	message.send_buf   = send_buf;
	message.recv_buf   = QE_NULL;
	message.length     = send_length;
	message.cs_take    = 1;
	message.cs_release = 0;
	message.next       = QE_NULL;

	n = bus->ops->xfer(spi, &message);
	if (n <= 0) {
		qe_error("spi bus send error:%d", n);
		goto __exit;
	}

	/* recv data */
	message.send_buf   = QE_NULL;
	message.recv_buf   = recv_buf;
	message.length     = recv_length;
	message.cs_take    = 0;
	message.cs_release = 1;
	message.next       = QE_NULL;

	n = bus->ops->xfer(spi, &message);

__exit:
	/* dont't forget release lock */
	lock->release(lock);
	return n;
}

/**
 * @brief Spi transfer with lock
 * @param[in] dev: spi device
 * @param[in] send_buf: send buffer
 * @param[in] recv_buf: recv buffer
 * @param[in] length: transfer length
 * @param[in] wait: wait timeout
 * @return >0:success <0:error
 */
qe_int qe_spi_transfer_wait(qe_spi_dev *spi, qe_const_ptr send_buf,
	qe_ptr recv_buf, qe_size length, qe_uint wait)
{
	qe_int n;
	qe_ret ret;
	qe_lock *lock;
	qe_spi_bus *bus;
	qe_spi_message message;

	qe_assert(spi != QE_NULL);
	qe_assert(spi->bus != QE_NULL);

	bus = spi->bus;
	lock = &spi->bus->lock;

	if (!lock->lock) {
		qe_error("spi bus %s not support lock", bus->parent.name);
		return -(qe_err_notsupport);
	}

	ret = lock->acquire(lock, wait);
	if (ret != qe_ok) {
		qe_error("spi bus %s lock acquire error:", bus->parent.name);
		return -(ret);
	}

	if (bus->owner != spi) {
		/* not the same owner as current, re-configure SPI bus */
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			qe_error("spi bus %s config error:%d", ret);
			return -(ret);
		} else {
			/* set SPI bus owner */
			bus->owner = spi;
			qe_debug("spi bus %s set owner %s", bus->parent.name, 
				spi->parent.name);
		}
	}

	/* initial message */
	message.send_buf   = send_buf;
	message.recv_buf   = recv_buf;
	message.length     = length;
	message.cs_take    = 1;
	message.cs_release = 1;
	message.next       = QE_NULL;

	/* transfer message */
	n = bus->ops->xfer(spi, &message);

	/* don't forget release lock */
	lock->release(lock);

	return n;
}

/**
 * @brief Spi transfer message list with lock
 * @param[in] spi: spi device
 * @param[in] message: message list
 * @return success transferd message index
 */
qe_spi_message *qe_spi_transfer_message_wait(qe_spi_dev *spi,
	qe_spi_message *message, qe_uint wait)
{
	qe_int n;
	qe_ret ret;
	qe_lock *lock;
	qe_spi_bus *bus;
	qe_spi_message *index;

	qe_assert(spi != QE_NULL);

	if (!spi || !message) {
		qe_error("invalid params");
		return QE_NULL;
	}

	if (!spi->bus) {
		qe_error("spi dev %s no bus", spi->parent.name);
		return QE_NULL;
	}

	bus = spi->bus;
	if (!bus->lock.lock || !bus->lock.acquire || !bus->lock.release) {
		qe_error("spi bus %s no lock", bus->parent.name);
		return QE_NULL;
	}

	lock = &bus->lock;
	ret = lock->acquire(lock, wait);
	if (ret != qe_ok) {
		qe_error("spi bus %s lock error:%d", bus->parent.name, ret);
		return QE_NULL;
	}

	index = QE_NULL;

	if (bus->owner != spi) {
		ret = bus->ops->configure(spi, &spi->config);
		if (ret != qe_ok) {
			qe_error("spi bus %s config error:%d", bus->parent.name);
			goto __exit;
		} else {
			bus->owner = spi;
			qe_debug("spi bus %s set owner %s", bus->parent.name, 
				spi->parent.name);
		}
	}

	/* transmit each message */
	index = message;
	while (index != QE_NULL) {
		n = bus->ops->xfer(spi, index);
		if (n <= 0) {
			qe_error("msg %p xfer error:%d", index, n);
			goto __exit;
		}

		index = index->next;
	}

__exit:
	/* don't forget release lock */
	lock->release(lock);
	return index;
}